package napier.edu.chrisMann.retroller;

/**
 *
 * @author 09013030
 */

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class RetrollerServer {

    //This is the full server class

    //This is the serversocket that will be used for listening
    private ServerSocket ListeningSocket;
    
    //This socket will be used to accept sockets from the above
    private Socket ClientSocket;

    //This is the port on which the server listens
    private final int LISTENING_PORT = 2004;

    //Each iteration of the loop, we need to check this flag.
    //If the flag is false, we end the server. This means we
    //can remotely end the server. Alternatively, we can simply
    //end the main process.
    private boolean exitFlag = false;

    //This flag is used to check whether or not we have a connected client.
    private boolean clientConnected = false;

    //This DataInputStream uses TCP to send data across the network.
    //This is what we will use to receive input strings from the client.
    private DataInputStream DInputStream;

    //This collection holds all of the Robot threads (event handlers) currently
    //simulating keypresses.
    private Map<String, EventHandler> eventHandlers
            = new HashMap<String, EventHandler>();

    //This collection maps all of the key shorthands to integers.
    //Why integers? Because the java robot prefers to have the ascii reference
    //to keys when simulating their pressing. In this way, we can pass the
    //correct integer as defined in the static constants of the KeyEvent
    //standard library class.
    private Map<String, Integer> keyMap
            = new HashMap<String, Integer>();


    //Nothing new here, just branching off and calling initialize.
    public static void main(String[] args) {
        RetrollerServer CServer = new RetrollerServer();
        try {
            CServer.initialize();
        } catch (IOException ex) {
            Logger.getLogger(RetrollerServer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void initialize() throws IOException{

        //Now we are constructing the listening ServerSocket to listen
        //on the port number we defined in the attributes section of this class.
        ListeningSocket = new ServerSocket(LISTENING_PORT);

        //Now, we must map all of our shorthand key strings to their integer
        //ascii counterparts.

        // DIRECTION BUTTONS //
        keyMap.put("LT", KeyEvent.VK_LEFT);     //LEFT
        keyMap.put("RT", KeyEvent.VK_RIGHT);    //RIGHT
        keyMap.put("UP", KeyEvent.VK_UP);       //UP
        keyMap.put("DN", KeyEvent.VK_DOWN);     //DOWN

        // ACTION BUTTONS //
        keyMap.put("B0", KeyEvent.VK_NUMPAD0);  //BUTTON 0
        keyMap.put("B1", KeyEvent.VK_NUMPAD1);  //BUTTON 1
        keyMap.put("B2", KeyEvent.VK_NUMPAD2);  //BUTTON 2
        keyMap.put("B3", KeyEvent.VK_NUMPAD3);  //BUTTON 3

        // SHOULDERS //
        keyMap.put("S1", KeyEvent.VK_DELETE); //LEFT SHOULDER
        keyMap.put("S2", KeyEvent.VK_PAGE_DOWN); //RIGHT SHOULDER


        // PROCESS BUTTONS //
        keyMap.put("ST", KeyEvent.VK_ENTER);    //START
        keyMap.put("SE", KeyEvent.VK_SHIFT);    //SELECT

        //This is just for debug, it shows in the
        //console that we've gotten this far.
        System.out.println("System is up and running.");


        while(exitFlag == false){ //While the server hasn't been told to stop
            if(!clientConnected){    //Check if there is a client connected
                WaitForConnection(ClientSocket); //Wait for a client to connect if there isn't
            } else { //If there is
                Process(DInputStream, ClientSocket, ListeningSocket); //Process that clients input
            }
        }

        if(exitFlag == true){
            System.out.println("Shutting down");
        }
    }

    public void Process(DataInputStream DInputStream,
            Socket ClientSocket,
            ServerSocket ListeningSocket)
            throws IOException{
        //This is that function in every program that does 80% of work
        //This will store the data from the inputstream (in our case, a string)
        String rawEvent = null;

        //We assign rawEvent the value of whatever is coming down the tubes,
        //but we want it parsed in UTF, so that it's readable.
        //This function, readUTF will be passing us ALL of the commands from
        //the remote device.
        rawEvent = DInputStream.readUTF();


        try
        {
            //The command that must be processed is disconnect, because in this
            //case, the remainder of this function need not be processed
            //(remember that the qualifier for running this function is
            //whether or not we have a client; well - if we've just received a
            //disconnect - we no longer have a client, so get out of this
            //function, and close the bridge between this server and the client)
            if(rawEvent.equals("DISC")){
                clientConnected = false;
                System.out.println("Received disconnection command. Closing client socket.");
                ClientSocket.close();
                return;
            }

            if(rawEvent.equals("STOP")){
                System.out.println("Server received order to stop listening and terminate.");
                exitFlag = true;
                clientConnected = false;
                return;
            }

            //I used to work a lot with raw data, parsing it in PHP. Whenever I
            //had a list of data I would separate them using a comma (,) as a
            //delimiter. If I had an object with two counterparts, I would use
            //a colon. So that's what I've chosen to use in this case. On the
            //left hand side of the colon is the button in question and on the
            //right is the direction the key should be in.

            //"RT:DN" means "tell the server I want the RIGHT key pushed DOWN
            //"RT:UP" means "tell the server I want the RIGHT key released.

            //This line says "give me an array with two indices, the part on
            //the left of the colon, and the part on the right. Drop the colon."
            String[] splitRawEvent = rawEvent.split(":");
            String key = splitRawEvent[0];
            String direction = splitRawEvent[1];
            //The first thing we MUST do when receiving an order is to check if
            //there is a handler associated with this key. We don't want
            //multiple thread pushing the same key.
            if(eventHandlers.containsKey(key)){
                if(direction.equals("UP")){ //If the direction is UP
                    System.out.println("(Stopping thread) Received UP command for key: " + key.toString() + "(" + keyMap.get(key) + ")");
                        eventHandlers.get(key).Stop(); //Stop pushing the key immediately
                        eventHandlers.remove(key); //Remove this handler from the collection
                }
            } else {
                if(direction.equals("DN")){
                    //If we've reached this stage then there is no handler for this key
                    //and the direction the key is to be pushed is down
                    System.out.println("(Starting thread) Received DN command for key: " + key + "(" + keyMap.get(key) + ")");
                    
                    //Create a new eventhandler object with the key shorthand in question
                    EventHandler Handler = new EventHandler(keyMap.get(key));

                    //Add this eventhandler to the collection
                    eventHandlers.put(key, Handler);

                    //Fetch the eventhandler again and call start on
                    //its associated thread.
                    eventHandlers.get(key).T.start();
                }
            }
        } catch (Exception e){
            if(e.getMessage()!=null){System.err.println(e.getMessage());}
        }
    }
    
    public void WaitForConnection(Socket ClientSocket) throws IOException{

        System.out.println("Waiting for connection...");

        //This will effectively wait until a connection is established, and
        //then assign to clientSocket the details of that connection.
        ClientSocket = ListeningSocket.accept();

        //This assigns to inputStream the stream coming from the clientSocket connection
        DInputStream = new DataInputStream(ClientSocket.getInputStream());

        //If the incoming command is "CONN", there is a client connected.
        //This means that after this function has ended, it will not run again
        //until clientConnected == false
        if(DInputStream.readUTF().equals("CONN")){
            clientConnected = true;
            System.out.println("System has received a connection from the client.");
        }
    }
}

class EventHandler implements Runnable {

    //This flag determines when the handler should cease to exist
    private boolean isContinue = true;

    //This is a robot, it simulates keypress. The robots will be doing all
    //the work.
    private Robot KeyRobot;

    //This is the integer counterpart of the key we wish to simulate
    private int keyCode;

    Thread T;

    public EventHandler(int keyCode) throws AWTException {
        this.keyCode = keyCode;

        //Instantiate a new Thread and a new Robot
        T = new Thread(this, "Robot Thread["+keyCode+"]");
        KeyRobot = new Robot();
    }

    public void run() {
        //This lets us know that the robot thread has been started.
        //System.out.println(this.T.getName() + " is awake and about to start keypresses.");
        //A visual confirmation that the key is about to be held.
        //System.out.println(this.T.getName() + " pressing key " + keyCode + "(" + KeyEvent.getKeyText(keyCode) + ")");
        KeyRobot.keyPress(keyCode);
    }

    public void Stop(){
        KeyRobot.keyRelease(keyCode);
        this.isContinue = false;
        //System.out.println(this.T.getName() + " has received order to terminate keypresses.");
        //System.out.println(this.T.getName() + " releasing key " + keyCode + "(" + KeyEvent.getKeyText(keyCode) + ")");
    }
}
